業務アプリでは、「誰が・いつ・何をしたか」を残す監査ログや操作ログが必須です。 SQLiteは軽量で高速なためログ用途に向いていますが、 設計を誤ると肥大化・ロック・パフォーマンス低下が起きます。 この記事では、SQLiteでログを安全かつ高速に扱うための実務パターンをまとめます。
・監査ログ・操作ログの違いと設計方針
・SQLiteログテーブルの最適な構造
・高速INSERTの実装(C#)
・ローテーション(世代管理)
・セキュリティとバックアップ
・業務アプリ向けベストプラクティス
1. 監査ログと操作ログの違い
■ 監査ログ(Audit Log)
- 誰が何をしたかを証跡として残す
- 改ざんされてはいけない
- 削除・更新は原則禁止(追記のみ)
■ 操作ログ(Operation Log)
- ユーザー操作やアプリ動作の記録
- デバッグ・トラブルシューティング用途
- ローテーション(古いログ削除)が必要
用途に応じてテーブルを分けるのが基本です。
2. SQLiteログテーブルの設計
■ 監査ログテーブル例(追記専用)
CREATE TABLE AuditLogs (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
UserId TEXT NOT NULL,
Action TEXT NOT NULL,
Detail TEXT,
CreatedAt TEXT NOT NULL
);
■ 操作ログテーブル例(ローテーション前提)
CREATE TABLE OperationLogs (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Level TEXT NOT NULL, -- INFO / WARN / ERROR
Message TEXT NOT NULL,
CreatedAt TEXT NOT NULL
);
■ 設計ポイント
- CreatedAt は ISO8601(UTC)で保存 → ソートしやすい
- Detail は TEXT でOK(JSONも可)
- 監査ログは UPDATE/DELETE 禁止
3. C#での高速INSERT(ログはとにかく速さが命)
ログは頻繁に書き込まれるため、 高速・非同期・短時間トランザクションが重要です。
■ 最速パターン:プリペアドステートメント+トランザクション
using Microsoft.Data.Sqlite;
public class LogWriter
{
private readonly SqliteConnection _con;
private readonly SqliteCommand _cmd;
public LogWriter(string cs)
{
_con = new SqliteConnection(cs);
_con.Open();
_cmd = _con.CreateCommand();
_cmd.CommandText = @"
INSERT INTO OperationLogs (Level, Message, CreatedAt)
VALUES (@level, @msg, @createdAt);
";
_cmd.Parameters.Add("@level", SqliteType.Text);
_cmd.Parameters.Add("@msg", SqliteType.Text);
_cmd.Parameters.Add("@createdAt", SqliteType.Text);
_cmd.Prepare();
}
public void Write(string level, string message)
{
using var tran = _con.BeginTransaction();
_cmd.Parameters["@level"].Value = level;
_cmd.Parameters["@msg"].Value = message;
_cmd.Parameters["@createdAt"].Value = DateTime.UtcNow.ToString("o");
_cmd.ExecuteNonQuery();
tran.Commit();
}
}
プリペアドステートメント+トランザクションで 1件あたりのINSERTが圧倒的に高速になります。
4. 非同期ログ(UIフリーズ防止)
WPF/WinFormsでは、ログ書き込みをUIスレッドで行うと固まるため、 バックグラウンドキュー方式が最適です。
■ BlockingCollection を使ったログキュー
private readonly BlockingCollection<string> _queue = new();
public void StartWorker()
{
Task.Run(() =>
{
foreach (var msg in _queue.GetConsumingEnumerable())
{
Write("INFO", msg); // 上のWriteメソッド
}
});
}
public void Log(string message)
{
_queue.Add(message);
}
これでログは非同期で安全に直列化され、 database is locked の発生も防げます。
5. ローテーション(古いログの削除)
操作ログは無限に増えるため、 一定期間で削除 or 別ファイルに退避する必要があります。
■ 例:30日より古いログを削除
DELETE FROM OperationLogs
WHERE CreatedAt < datetime('now', '-30 days');
■ C#で定期実行
public void Cleanup()
{
using var con = new SqliteConnection(_cs);
con.Open();
var sql = @"
DELETE FROM OperationLogs
WHERE CreatedAt < datetime('now', '-30 days');
";
new SqliteCommand(sql, con).ExecuteNonQuery();
}
監査ログは削除しないため、 監査ログと操作ログは必ずテーブルを分けるべきです。
6. JSONログで柔軟性を高める
Detail を JSON にすると、 スキーマ変更なしで柔軟にログ内容を増やせます。
■ JSONログ例
{
"Before": { "Name": "Taro" },
"After": { "Name": "Jiro" },
"Ip": "192.168.1.10"
}
SQLiteは JSON 型を持ちませんが、TEXT で問題なく扱えます。
7. セキュリティ:ログは機密情報の宝庫
ログには個人情報・操作履歴・エラー内容が含まれるため、 セキュリティ対策が必須です。
■ 注意点
- ログにパスワード・トークンを絶対に書かない
- DBファイルの配置場所と権限を制限する
- 必要に応じて暗号化SQLite or カラム暗号化
- バックアップファイルの持ち出しに注意
8. 業務アプリ向けベストプラクティス
- 監査ログ(追記専用)と操作ログ(ローテーション)は分ける
- INSERTはプリペアドステートメント+トランザクションで高速化
- ログ書き込みはバックグラウンドキューで非同期化
- 操作ログは定期的に削除(30日など)
- 監査ログは削除禁止・改ざん禁止
- DetailはJSONにすると柔軟性が高い
- ログDBの権限・暗号化・バックアップをセットで考える
まとめ:SQLiteのログ設計は“速さ・安全性・運用”のバランスが鍵
- SQLiteはログ用途に非常に向いている
- ただし、肥大化・ロック・セキュリティに注意
- 非同期キュー+高速INSERTで安定運用できる
- 監査ログと操作ログは目的が違うため分離が必須
「とりあえずINSERTしておけばいい」ではなく、 「壊れず・速く・安全に残す」ログ設計が業務アプリの品質を決めます。 この記事のパターンをベースに、あなたのアプリに最適なログ基盤を構築してみてください。